package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.HotArticleService;
import com.heima.common.consatnts.article.ArticleConstants;
import com.heima.common.exception.CustException;
import com.heima.feigns.AdminFeign;
import com.heima.model.admin.pojos.AdChannel;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.app.AggBehaviorDTO;
import com.heima.utils.common.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * author by itheima
 *
 * @Date 2022/3/13
 * Description
 */
@Service
@Slf4j
public class HotArticleServiceImpl implements HotArticleService {
    /**
     * 计算热文章
     */
    @Autowired
    ApArticleMapper apArticleMapper;

    @Override
    public void computeHotArticle() {
        //1.查询近五天的文章
        String params = LocalDateTime.now().minusDays(5).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        List<ApArticle> apArticleList = apArticleMapper.selectArticleByDate(params);
        //2.计算近五天文章的热度得分
        List<HotArticleVo> hotArticleVoList = getHotArticleVoList(apArticleList);
        if (CollectionUtils.isEmpty(hotArticleVoList)) {
            log.info("太冷清了，近五天一片文章也没有");
        }
        //3.按照频道缓存文章
        cacheToRedisByTag(hotArticleVoList);
    }

    /**
     * 重新计算文章热度  更新redis缓存
     *
     * @param aggBehavior
     */
    @Override
    public void updateApArticle(AggBehaviorDTO aggBehavior) {
        // 1. 根据文章id 查询文章数据   (判断空)
        ApArticle article = apArticleMapper.selectById(aggBehavior.getArticleId());
        if (article == null) {
            CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST, "文章不存在");
        }
        // 2. 根据聚合数据  更改文章行为值
        int views = (int) (article.getViews() == null ? 0 : article.getViews() + aggBehavior.getView());
        article.setViews(views);
        int likes = (int) (article.getLikes() == null ? 0 : article.getLikes() + aggBehavior.getLike());
        article.setLikes(likes);
        int comment = (int) (article.getComment() == null ? 0 : article.getComment() + aggBehavior.getComment());
        article.setComment(comment);
        int collection = (int) (article.getCollection() == null ? 0 : article.getCollection() + aggBehavior.getCollect());
        article.setCollection(collection);
        //更新数据库
        apArticleMapper.updateById(article);
        // 3. 重新计算文章热度得分
        Integer score = computeScore(article);
        // 4. 判断文章发布时间  是否是今天  如果是  热度乘3
        String nowStr = DateUtils.dateToString(new Date());
        String publishStr = DateUtils.dateToString(article.getPublishTime());
        if (nowStr.equals(publishStr)) {
            score = score * 3;
        }
        // 5.  基于当前文章 替换该频道热度较低的文章
        updateArticleCache(score, article, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + article.getChannelId());
        // 6.  基于当前文章 替换推荐频道热度较低的文章
        updateArticleCache(score, article, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);

    }

    /**
     * 替换热度较低的文章
     * @param score
     * @param article
     * @param cacheKey
     */
    private void updateArticleCache(Integer score, ApArticle article, String cacheKey) {
        // 1. 从redis中查询对应的热点文章
        String hotArticleJson = redisTemplate.opsForValue().get(cacheKey);

        if (StringUtils.isBlank(hotArticleJson)) {
            return;
        }
        // 2. 判断当前文章是否在热点文章中存在
        List<HotArticleVo> hotArticleVoList = JSON.parseArray(hotArticleJson, HotArticleVo.class);
        //     如果存在  替换热度得分
        boolean isHas = false;
        for (HotArticleVo articleVo : hotArticleVoList) {
            if (articleVo.getId().equals(article.getId())) {
                articleVo.setScore(score);
                isHas = true;
                break;
            }
        }
        // 3. 如果不存在，将当前文章封装成HotArticleVo 保存到热点文章列表中
        if (!isHas) {
            hotArticleVoList.add(parseArticleVo(article));
        }
        // 4. 重新将热点文章排序  并 截取 前30条文章
        hotArticleVoList = hotArticleVoList.stream()
                .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30).collect(Collectors.toList());
        // 5.  更新缓存
        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(hotArticleVoList));
    }

    @Autowired
    AdminFeign adminFeign;

    /**
     * 按照频道  缓存文章到redis
     *
     * @param hotArticleVoList
     */
    private void cacheToRedisByTag(List<HotArticleVo> hotArticleVoList) {
        //1.远程查询频道信息
        ResponseResult<List<AdChannel>> result = adminFeign.selectChannels();
        if (!result.checkCode()) {
            CustException.cust(AppHttpCodeEnum.REMOTE_SERVER_ERROR, "远程调用频道信息失败");
        }
        //远程获得的频道数据
        List<AdChannel> channelList = result.getData();
        if (CollectionUtils.isEmpty(channelList)) {
            CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST, "频道信息为空");
        }
        //2.遍历频道 每个频道缓存热度较高的文章
        for (AdChannel channel : channelList) {
            List<HotArticleVo> hotArticleByTag = hotArticleVoList.stream().filter(articleVo -> articleVo.getChannelId().equals(channel.getId()))
                    .collect(Collectors.toList());
            //缓存 (集合数据， 缓存key)
            sortAndCache(hotArticleByTag, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + channel.getId());
        }
        //3.单独的推荐频道   缓存全部文章热度较高30条
        //缓存(集合数据，缓存key)
        sortAndCache(hotArticleVoList, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    /**
     * 缓存到redis中
     *
     * @param hotArticleByTag
     * @param hotArticleFirstPage
     * @param id
     */
    @Autowired
    StringRedisTemplate redisTemplate;

    private void sortAndCache(List<HotArticleVo> hotArticleList, String cacheKey) {
        //文章热度降序排行  截取前30条
        hotArticleList = hotArticleList.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30)
                .collect(Collectors.toList());
        //使用redisTemplates缓存数据  String json【】
        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(hotArticleList));
    }

    /**
     * h获得热度得分
     *
     * @param apArticleList
     */
    private List<HotArticleVo> getHotArticleVoList(List<ApArticle> apArticleList) {
        return apArticleList.stream().map(this::parseArticleVo).collect(Collectors.toList());
    }

    /**
     * 将article 转为 articleVO  (score 计算热度得分)
     *
     * @param apArticle
     * @return
     */
    private HotArticleVo parseArticleVo(ApArticle apArticle) {
        //copy 一个新实体类
        HotArticleVo hotArticleVo = new HotArticleVo();
        BeanUtils.copyProperties(apArticle, hotArticleVo);
        //计算文章得分
        Integer score = computeScore(apArticle);
        hotArticleVo.setScore(score);
        return hotArticleVo;
    }

    /**
     * 按照文章不同行为指标的值  计算文章得分
     *
     * @param apArticle
     * @return
     */
    private Integer computeScore(ApArticle apArticle) {
        Integer score = 0;
        if (apArticle.getViews() != null) {
            score += apArticle.getViews() * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        if (apArticle.getLikes() != null) {
            score += apArticle.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        if (apArticle.getComment() != null) {
            score += apArticle.getViews() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        if (apArticle.getCollection() != null) {
            score += apArticle.getViews() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        return score;
    }
}
